<template>
  <div class="base64-converter">
    <h1>Base64 文件转换器</h1>
    <p class="description">将文件转换为 Base64 编码，或将 Base64 编码转换回文件。</p>

    <Tabs v-model:activeKey="activeTab">
      <!-- 文件转Base64 -->
      <TabPane key="encode" tab="文件转Base64">
        <div class="drop-zone" @drop.prevent="handleDrop" @dragover.prevent @dragenter.prevent :class="{ 'drag-over': isDragOver }" @dragenter="isDragOver = true" @dragleave="isDragOver = false">
          <div class="upload-content">
            <upload-outlined class="upload-icon" />
            <p>将文件拖放到此处，或点击选择文件</p>
            <input type="file" ref="fileInput" @change="handleFileSelect" class="file-input" />
            <Button type="primary" @click="triggerFileInput">选择文件</Button>
          </div>
        </div>

        <div v-if="base64Result" class="result-area">
          <TextArea v-model:value="base64Result" :rows="6" placeholder="转换后的Base64将显示在这里..." readonly />
          <Button type="primary" @click="copyToClipboard" class="copy-btn"> 复制Base64 </Button>
        </div>
      </TabPane>

      <!-- Base64转文件 -->
      <TabPane key="decode" tab="Base64转文件">
        <TextArea v-model:value="base64Input" :rows="6" placeholder="请输入Base64字符串..." @change="validateBase64Input" />
        <div class="button-group">
          <Button type="primary" @click="convertToFile" :disabled="!isValidBase64"> 下载文件 </Button>
        </div>
      </TabPane>
    </Tabs>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { message, Input, Tabs, Button } from 'ant-design-vue'
import { UploadOutlined } from '@ant-design/icons-vue'

// 响应式状态
const activeTab = ref('encode')
const fileInput = ref(null)
const base64Result = ref('')
const base64Input = ref('')
const isDragOver = ref(false)
const currentFileName = ref('')
const currentFileType = ref('')

// 验证Base64输入是否有效
const isValidBase64 = computed(() => {
  if (!base64Input.value) return false
  try {
    return btoa(atob(base64Input.value)) === base64Input.value
  } catch (e) {
    return false
  }
})

// 触发文件选择
const triggerFileInput = () => {
  fileInput.value.click()
}

// 处理文件选择
const handleFileSelect = (event) => {
  const file = event.target.files[0]
  if (file) {
    convertFileToBase64(file)
  }
}

// 处理文件拖放
const handleDrop = (event) => {
  isDragOver.value = false
  const file = event.dataTransfer.files[0]
  if (file) {
    convertFileToBase64(file)
  }
}

// 将文件转换为Base64
const convertFileToBase64 = (file) => {
  const reader = new FileReader()
  reader.onload = () => {
    // 保存完整的 data URL，包含文件类型信息
    base64Result.value = reader.result
    currentFileName.value = file.name
    currentFileType.value = file.type || 'application/octet-stream'
    message.success('文件转换成功！')
  }
  reader.onerror = () => {
    message.error('文件读取失败，请重试！')
  }
  reader.readAsDataURL(file)
}

// 复制到剪贴板
const copyToClipboard = async () => {
  try {
    // 如果需要纯Base64，去掉data URL前缀
    const base64String = base64Result.value.split(',')[1]
    await navigator.clipboard.writeText(base64String)
    message.success('已复制到剪贴板！')
  } catch (err) {
    message.error('复制失败，请手动复制！')
  }
}

// 验证Base64输入
const validateBase64Input = () => {
  if (base64Input.value && !isValidBase64.value) {
    message.error('请输入有效的Base64字符串！')
  }
}

// 将Base64转换为文件并下载
const convertToFile = () => {
  try {
    let base64Data = base64Input.value
    let fileType = 'application/octet-stream'
    let fileName = currentFileName.value || 'downloaded-file'

    // 处理可能包含的 data URL 头部
    if (base64Data.includes('data:')) {
      const [header, content] = base64Data.split(',')
      const matches = header.match(/^data:([^;]+);base64/)
      if (matches && matches[1]) {
        fileType = matches[1]
      }
      base64Data = content
    } else {
      // 如果输入的是纯Base64字符串，尝试使用之前保存的文件类型
      fileType = currentFileType.value || fileType
    }

    // 解码Base64
    try {
      const byteCharacters = atob(base64Data)
      const byteNumbers = new Array(byteCharacters.length)
      for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i)
      }
      const byteArray = new Uint8Array(byteNumbers)

      // 尝试通过魔数检测文件类型
      if (!base64Data.includes('data:') && fileType === 'application/octet-stream') {
        fileType = detectFileType(byteArray)
      }

      // 如果没有文件名，根据类型生成默认文件名
      if (!fileName || fileName === 'downloaded-file') {
        // 根据文件类型设置扩展名
        switch (fileType) {
          case 'image/jpeg':
            fileName = 'image.jpg'
            break
          case 'image/png':
            fileName = 'image.png'
            break
          case 'image/gif':
            fileName = 'image.gif'
            break
          case 'application/pdf':
            fileName = 'document.pdf'
            break
          case 'text/plain':
            fileName = 'text.txt'
            break
          case 'application/zip':
            fileName = 'archive.zip'
            break
          case 'application/x-rar-compressed':
            fileName = 'archive.rar'
            break
          case 'application/x-msdownload':
            fileName = 'program.exe'
            break
          case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
            fileName = 'document.docx'
            break
          case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
            fileName = 'spreadsheet.xlsx'
            break
          case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
            fileName = 'presentation.pptx'
            break
          default:
            fileName = 'file.bin'
        }
      }

      const blob = new Blob([byteArray], { type: fileType })

      const link = document.createElement('a')
      link.href = URL.createObjectURL(blob)
      link.download = fileName
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(link.href)

      message.success('文件已开始下载！')
    } catch (err) {
      message.error('转换失败，请检查输入是否正确！')
    }
  } catch (err) {
    message.error('转换失败，请检查输入是否正确！')
  }
}

// 通过文件头魔数检测文件类型
const detectFileType = (byteArray) => {
  const magicNumbers = {
    // 图片格式
    'image/jpeg': [[0xff, 0xd8, 0xff]],
    'image/png': [[0x89, 0x50, 0x4e, 0x47]],
    'image/gif': [[0x47, 0x49, 0x46, 0x38]],

    // 文档格式
    'application/pdf': [[0x25, 0x50, 0x44, 0x46]],

    // 压缩格式
    'application/zip': [[0x50, 0x4b, 0x03, 0x04]],
    'application/x-rar-compressed': [[0x52, 0x61, 0x72, 0x21]],

    // 可执行文件
    'application/x-msdownload': [[0x4d, 0x5a]],

    // Office文档
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document': [[0x50, 0x4b, 0x03, 0x04]],
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': [[0x50, 0x4b, 0x03, 0x04]],
    'application/vnd.openxmlformats-officedocument.presentationml.presentation': [[0x50, 0x4b, 0x03, 0x04]]
  }

  // 检查文件头部字节是否匹配已知的魔数
  for (const [mimeType, signatures] of Object.entries(magicNumbers)) {
    for (const signature of signatures) {
      let matches = true
      for (let i = 0; i < signature.length; i++) {
        if (byteArray[i] !== signature[i]) {
          matches = false
          break
        }
      }
      if (matches) {
        return mimeType
      }
    }
  }

  // 如果没有匹配的魔数，尝试检测是否为文本文件
  const isText = [...byteArray.slice(0, 1000)].every((byte) => byte < 0x7f)
  if (isText) return 'text/plain'

  return 'application/octet-stream'
}

const { TextArea } = Input
const { TabPane } = Tabs
</script>

<style scoped>
.base64-converter {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.description {
  color: #666;
  margin-bottom: 20px;
}

.drop-zone {
  border: 2px dashed #d9d9d9;
  border-radius: 4px;
  padding: 20px;
  text-align: center;
  background: #fafafa;
  transition: all 0.3s;
  margin-bottom: 20px;
}

.drag-over {
  border-color: #1890ff;
  background: #e6f7ff;
}

.upload-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.upload-icon {
  font-size: 48px;
  color: #999;
}

.file-input {
  display: none;
}

.result-area {
  margin-top: 20px;
}

.copy-btn {
  margin-top: 10px;
}

.button-group {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}

:deep(.ant-tabs-nav) {
  margin-bottom: 20px;
}
</style>
